﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using Utils;

namespace TechnicalIndicators
{
	public abstract class GeneratorBase<TSource, TDestination> : IDisposable where TSource : class where TDestination : class 
	{
		#region Attributes

		private ReadWriteLockedObservableCollection<TSource> _source;
		
		private bool _started;

		#endregion

		#region Properties

		public TDestination LastValue { get; private set; }
		public ReadWriteLockedObservableCollection<TDestination> Serie { get; private set; }

		public ReadWriteLockedObservableCollection<TSource> Source
		{
			get
			{
				return _source;
			}
			set
			{
				if (_started)
				{
					throw new InvalidOperationException("The generator cannot be configured while it's started");
				}
				_source = value;				
			}
		}

		#endregion

		#region Public Methods
		
		protected GeneratorBase()
		{
			this.Serie = new ReadWriteLockedObservableCollection<TDestination>(Properties.Settings.Default.MaxWaitWriteLockSeries);
		}

		protected GeneratorBase(ObservableCollection2<TDestination> destination)
		{
			this.Serie = new ReadWriteLockedObservableCollection<TDestination>(destination, Properties.Settings.Default.MaxWaitWriteLockSeries);
		}

		public virtual void Start()
		{
			if (_source == null)
			{
				throw new InvalidOperationException("You must configure the generator before starting it");
			}

			try
			{
				this.AddPoints(_source.GetReadLock());
				_source.CollectionChanged += Source_CollectionChanged; // Disposed
				_started = true;
			}
			finally
			{
				_source.FreeReadLock();
			}
		}

		public virtual void Clear()
		{
			try
			{
				if (_source != null)
				{
					_source.CollectionChanged -= Source_CollectionChanged; // Disposed					
				}
				this.Serie.GetWriteLock().Clear();
				this.LastValue = null;
				_started = false;
			}
			finally
			{
				this.Serie.FreeWriteLock();
			}
		}

		protected abstract TDestination CreatePoint(TSource sourcePoint);

		#endregion

		#region Event Handlers

		private void Source_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
		{
			if (e.Action == NotifyCollectionChangedAction.Add)
			{
				this.AddPoints(e.NewItems.Cast<TSource>());
			}
		}

		#endregion

		#region Private Methods

		private void AddPoints(IEnumerable<TSource> sourcePoints)
		{
			ObservableCollection2<TDestination> points = this.Serie.GetWriteLock();
			try
			{
				List<TDestination> tempPoints = new List<TDestination>();
				foreach (TSource sourcePoint in sourcePoints)
				{
					this.LastValue = this.CreatePoint(sourcePoint);
					
					if (this.LastValue != null)
					{
						tempPoints.Add(this.LastValue);	
					}
				}
				points.AddRange(tempPoints);
			}
			finally
			{
				this.Serie.FreeWriteLock();
			}
		}

		#endregion

		#region IDisposable

		protected bool Disposed { get; private set; }

		private void Dispose(bool disposing)
		{
			if (!this.Disposed)
			{
				this.InternalDispose(disposing);
			}

			this.Disposed = true;
		}

		protected virtual void InternalDispose(bool disposing)
		{
			if (disposing)
			{
				_source.CollectionChanged -= Source_CollectionChanged;
			}

			// Dispose here only unmanaged objects 
			// Don’t use managed objects here because maybe 
			// they have been finalized already
		}

		public void Dispose()
		{
			this.Dispose(true);
			GC.SuppressFinalize(this);
		}

		~GeneratorBase()
		{
			this.Dispose(false);
		}

		#endregion

	}
}
